#!/usr/bin/python
# -*- coding:utf-8 -*-

import datetime
import json
import ipaddress
from urllib.parse import urlparse

from wtforms.validators import DataRequired
from wtforms import fields, ValidationError

from app.log.logs import CCloudLog, CJobLog
from app.config.settings import SCANNER_IP, ASSET_TASK_INTERVAL
from app.libs.oms import Client
from app.validators import BaseForm
from app.libs.site_utils import domain_syntax_check
from app.libs.utils import (
    create_job,
    schedule_asset,
    enable_web_sec_task,
    enable_available_task,
)
from app.errors import ParameterError
from app.db.models.users import User, Role
from app.db.models.jobs import Job
from app.db.models.assets import Asset
from app.db.models.defense_targets import DefenseTargets
from app.db.models.packages import Packages
from app.db.models.balances import Balances
from app.db.models.alert_groups import AlertGroup
from app.db.models.alertreceivers import AlertReceivers
from app.db.models.asset_tasks import AssetTasks
from app.handler.v2_users import LoginHelper


class DefenseSourceCheckForm(BaseForm):

    username = fields.StringField(validators=(DataRequired(),))
    subdomain = fields.StringField(validators=(DataRequired(),))
    source_ip = fields.StringField(validators=(DataRequired(),))
    protocol = fields.StringField(validators=(DataRequired(),))

    def validate_username(self, value):
        """
        根据用户名查询云监测是否已经存在该用户， 不存在则创建
        1、创建用户时，不读cas的用户信息， 在用户登录查看任务结果时， 自动同步
        2、云防御只支持邮箱 或者 电话 作为用户名，  以此来创建默认告警联系人
        """
        username = value.data
        if user := User.objects.find({"username": username}).first():
            self.user = user
        else:
            self.user = self.create_user(username)

    def validate_subdomain(self, value):
        root_domain, domain = domain_syntax_check(value.data)
        if not root_domain:
            raise ValidationError("域名格式错误")

    def validate_protocol(self, value):
        if value.data not in ["http", "https"]:
            raise ValidationError("协议错误， 只支持http与https")

    def validate_for_api(self):
        obj = super(DefenseSourceCheckForm, self).validate_for_api()
        self.alert_group = AlertGroup.objects.filter(uid=self.user.id, isDefault=True).first()
        target_url = f"{self.protocol.data}://{self.subdomain.data}"
        source_ip = self.check_source_ip(self.source_ip.data)
        u_p = urlparse(target_url)
        port = u_p.port
        if not port:
            port = 443 if self.protocol.data == 'https' else 80
        job = Job.objects.find({"uid": obj.user.id, "targetUrl": target_url, "sourceIp": source_ip,
                                "target.port": port}).first()
        log_user_data = dict(log_type="ops", logSource="back", operator=self.username.data,
                             ip="is_defense", uid=str(self.user.id))
        if job and job.defense_target_id:
            CCloudLog(**log_user_data).info(
                action_message='源站安全检测',
                detail=f"云防联动-源站安全检测, 域名: {self.subdomain.data}, "
                       f"IP: {self.source_ip.data} 协议: {self.protocol.data}, 资产重复"
            )
            raise ParameterError(msg="重复任务")
        target = self.create_defense_targets(target_url, source_ip)
        if job:
            job.defense_target_id = target.id
            job.verification.isVerified = True
            job.cydEnabled = True
            job.save()
            asset = Asset.objects.find({"jobId": job.id}).first()
        else:
            job, asset = create_job(
                uid=self.user.id,
                target_url=target_url,
                note="源站安全检测",
                source_ip=source_ip,
                enable_monitor=False,
                enable_alert=False,
                defense_target_id=target.id
            )
            log_info = {
                'affects': [job.targetUrl],
                'actionMessage': '新增资产',
                'detail': f'云防联动-源站安全检测新增资产，资产名称：{job.note}，资产地址：{job.targetUrl}'
            }
            CJobLog(**log_user_data, **log_info).info(
                log_info['actionMessage'], log_info['detail'])
        # 云防御 IP白名单
        try:
            resp = self.idw(job.note, self.user.username, target_url)
        except:
            CCloudLog(**log_user_data, affects=[job.targetUrl]).info(
                action_message='新增扫描器IP白名单',
                detail=f"云防联动-源站安全检测资产名称：{job.note}，资产地址：{job.targetUrl} "
                       f",添加扫描器出口IP白名单，状态：失败，失败原因: 服务器错误"
            )
            return self
        else:
            status = resp.get('data', [])[0].get('status')
            msg = resp.get('data', [])[0].get('msg')
            status_msg = '成功' if status == 'success' else f"失败，失败原因：{msg}"
            CCloudLog(**log_user_data, affects=[job.targetUrl]).info(
                action_message='新增扫描器IP白名单',
                detail=f"云防联动-源站安全检测资产名称：{job.note}，资产地址：{job.targetUrl}  ,添加扫描器出口IP白名单，状态：{status_msg}"
            )
        if status != "success":
            return self

        # 发检测任务
        self.enable_monitor(job, asset)
        CCloudLog(**log_user_data, affects=[job.targetUrl]).info(
            action_message='源站安全检测',
            detail=f"云防联动-源站安全检测, 资产名称：{job.note}，资产地址：{job.targetUrl}, 完成检测任务下发 "
        )
        return self

    @staticmethod
    def create_user(username):
        return LoginHelper.create_user(username)

    def create_defense_targets(self, target_url, source_ip):
        target = DefenseTargets(
            target_url=target_url,
            source_ip=source_ip,
            target_name="源站安全检测",
            user_id=self.user.id
        ).save()
        return target

    def enable_monitor(self, job, asset):
        alert_setting = job.alertSettings
        is_defense_args = dict(
            is_defense=True,
            is_periodic=False,
        )
        conf = dict(
            immediate_exec=True,
            alert_setting=alert_setting,
            is_manual=True,
            **is_defense_args
        )
        aa_conf = {
            "name": "源站安全检测",
            "triggerType": "manual",
            "taskType": "",
            "targetUrl": job.targetUrl,
            "jobId": job.id,
            "uid": job.uid,
            "isVerified": job.verification.isVerified,
            "enableMonitor": True,
            "alertSettings": alert_setting,
            "is_defense_task": True
        }
        #  资产变动监测
        aa_task = AssetTasks(taskGroupType="web_asset", **aa_conf)
        asset.taskSettings.enable = True
        asset.taskSettings.interval = ASSET_TASK_INTERVAL
        schedule_asset(job, asset, asset.taskSettings, asset_task=aa_task, **conf)
        asset.save()
        aa_task.assetSettings = asset.taskSettings
        aa_task.save()

        # web 安全监测
        web_task = AssetTasks(taskGroupType="web_security", **aa_conf)
        web_task.save()
        enable_web_sec_task(web_task, job, **is_defense_args)
        # ping  任务
        ping_task = AssetTasks(taskGroupType="ping", **aa_conf)
        ping_task.pingSettings.target = f"{job.targetUrl}/"
        ping_task.pingSettings.name = "源站安全检测"
        ping_task.save()
        enable_available_task(ping_task, job, trigger_type="manual", **is_defense_args)

        # http 任务
        http_task = AssetTasks(taskGroupType="http", **aa_conf)
        http_task.httpSettings.target = f"{job.targetUrl}/"
        http_task.httpSettings.name = "源站安全检测"
        http_task.save()
        enable_available_task(http_task, job, trigger_type="manual", **is_defense_args)
        # 把任务数据更新到job
        for _t in ["http", "ping"]:
            settings = [getattr(a, f"{_t}Settings") for a in
                        AssetTasks.objects.filter(jobId=job.id, taskGroupType=_t) if getattr(a, f"{_t}Settings", {})]
            setattr(job, f"{_t}Settings", settings)
        job.save()

    def idw(self, name, username, target_url):
        target_list = [{
            'name': name,
            'username': username,
            'targetUrl': target_url}]
        params = {
            'assets': target_list,
            'ips': SCANNER_IP,
            'operator': "admin"
        }
        return Client().post('/api/inner/dnss/scanner_ip/', json.dumps(params)).get('ret', {})

    @staticmethod
    def check_source_ip(source_ip):
        try:
            ipaddress.ip_address(source_ip)
        except ValueError:
            return ""
        else:
            return source_ip
